﻿using FFmpeg.AutoGen;
using FFmpegLib;
using MediaLib.Interface;
using Silk.NET.OpenAL;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace MediaLib.Implement;

/// <summary>
/// 基于OpenAL的音频播放器
/// </summary>
public sealed unsafe class SilkAudioPlayer : IAudioPlayer
{
    #region OpenAL obj
    SilkOpenALUtils _openAL;
    private BufferFormat target_fmt;
    private AVSampleFormat out_sample_fmt;
    #endregion

    private CancellationTokenSource _cancelSource;

    public SourceState CurCtlStatus { get; set; }
    public string AudioFile { get; private set; }
    public int Size { get; private set; }
    public long Bitrate { get; private set; }
    public int ChannelNum { get; private set; }
    public int DepthBit { get; private set; }
    public int PerSampleSize { get; private set; }
    public int SampleRate { get; private set; }
    public FAVStream AudioStreamInfo { get; private set; }

    private FAudioFifo Fifo;
    private FSwrContext SwrCtx;

    public Action<FAVStream> DisplayInfo;

    public SilkAudioPlayer(string audioFile)
    {
        AudioFile = audioFile;
        if (string.IsNullOrEmpty(AudioFile)) { throw new ArgumentException("未指定音频文件"); }
        CurCtlStatus = SourceState.Initial;
    }

    public void Play()
    {
        InFmtCtx ??= new FAVFormatContext();
        if (!InFmtCtx.FmtCtxIsOpen)
            InFmtCtx.OpenFmtCtx(AudioFile);
        if (!InFmtCtx.FmtCtxIsOpen) { return; }

        DisplayInfo?.Invoke(InFmtCtx.AudioStream);
        _openAL = new SilkOpenALUtils();
        _openAL.Init();
        if (!_openAL.Initialization) { return; }

        PlayByFFmpeg();
    }

    /// <summary>
    /// 是否暂停
    /// </summary>
    /// <param name="pause">true:暂停;false:继续;</param>
    public void Pause(bool pause)
    {
        if (IsDisposed || CurCtlStatus == SourceState.Stopped)
            return;

        CurCtlStatus = pause ? SourceState.Paused : SourceState.Playing;
        SourceState curStat = _openAL.GetSourceState();
        if (pause && curStat == SourceState.Playing)
            _openAL.SourcePause();
        else if (!pause && curStat == SourceState.Paused)
            _openAL.SourcePlay();
    }

    public void Stop()
    {
        CurCtlStatus = SourceState.Stopped;
        _openAL.SourceStop();
        Fifo.Clear();
    }

    /// <summary>
    /// 跳转到指定秒
    /// </summary>
    /// <param name="seconds"></param>
    public void Seek(double seconds)
    {
        if (IsDisposed || CurCtlStatus == SourceState.Stopped)
            return;

        Pause(true);
        Fifo?.Clear();
        InFmtCtx.Seek(seconds);
        Pause(false);
    }

    public void AdjustVolume(float vol) => _openAL.SetVolume(vol);

    public void AdjustSpeed(float vol) => _openAL.SetSpeed(vol);

    public void GetAudioInfo(string audioFile, out long bitrate, out int sampleRate, out int channelNum, out int depthBit, out int size)
    {
        throw new NotImplementedException();
    }

    #region dispose
    public bool IsDisposed => disposedValue;
    private bool disposedValue;
    protected void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                disposedValue = true;
                if (!_cancelSource.IsCancellationRequested)
                    _cancelSource.Cancel();
                _cancelSource.Dispose();
                InFmtCtx?.Dispose();
                Fifo?.Dispose();
                SwrCtx?.Dispose();
                _openAL?.Dispose();
            }
            disposedValue = true;
        }
    }

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
    #endregion

    #region FFmpeg.AutoGen
    private FAVFormatContext InFmtCtx;

    public void PlayByFFmpeg()
    {
        AudioStreamInfo = InFmtCtx.AudioStream;

        CurCtlStatus = SourceState.Playing;
        _cancelSource = new CancellationTokenSource();
        Task.Factory.StartNew(LoadAudioWorkThread, _cancelSource.Token);
        Task.Factory.StartNew(PlayAudioWorkThread, _cancelSource.Token);
        _openAL.SourcePlay();
    }

    public void LoadAudioWorkThread()
    {
        using FAVPacket packet = new FAVPacket();
        using FAVFrame frame = new FAVFrame();
        while (!disposedValue)
        {
            if (NeedLoad())
                LoadByFFmpeg(packet, frame);
            else
                ffmpeg.av_usleep(10000u);

            packet.UnRef();
            frame.UnRef();
        }
        packet.Dispose();
        frame.Dispose();
    }

    /// <summary>
    /// 判断是否需要继续加载
    /// </summary>
    /// <returns></returns>
    private bool NeedLoad()
    {
        if (CurCtlStatus != SourceState.Playing)
            return false;
        if (Fifo == null)
            return true;
        int fifoToRead = Fifo.Size();
        return fifoToRead < 20000;
    }

    /// <summary>
    /// OpenAL播放状态控制任务
    /// </summary>
    public void PlayAudioWorkThread()
    {
        while (!disposedValue)
        {
            ffmpeg.av_usleep(10000);
            if (Fifo != null)
            {
                // 读取排队片段数
                int queuedNum = _openAL.BuffersQueued();
                // 读取当前源播放状态
                SourceState curStat = _openAL.GetSourceState();

                #region 控制播放状态
                if (CurCtlStatus == SourceState.Playing && queuedNum > 0 && curStat != SourceState.Playing)
                {
                    _openAL.SourcePlay();
                }
                else if (CurCtlStatus == SourceState.Paused && curStat == SourceState.Playing)
                {
                    _openAL.SourcePause();
                }
                else if (CurCtlStatus == SourceState.Stopped)
                {
                    _openAL.SourceStop();
                    Dispose();
                    break;
                }
                #endregion

                #region 检查为播放片段的排队数
                if (CurCtlStatus == SourceState.Playing && queuedNum < 3)
                {
                    void* pcmData = Fifo.Dequeue(5000, out int dataSize);
                    if (dataSize > 0)
                    {
                        _openAL.QueueBuffer(pcmData, dataSize, target_fmt, SampleRate);
                        System.Runtime.InteropServices.Marshal.FreeHGlobal(new IntPtr(pcmData));
                    }
                }
                #endregion
            }

            //检查并清除已播放的缓存
            _openAL.ClearUnqueueBuffers();
        }
    }

    /// <summary>
    /// 通过ffmpeg加载音频块
    /// </summary>
    /// <param name="packet"></param>
    /// <param name="frame"></param>
    /// <returns>采样数</returns>
    /// <exception cref="ArgumentException"></exception>
    private void LoadByFFmpeg(FAVPacket packet, FAVFrame frame)
    {
        if (InFmtCtx == null || !InFmtCtx.FmtCtxIsOpen)
            return;

        try
        {
            int ret = InFmtCtx.ReadPacket(packet);
            if (ret < 0 || packet.StreamIndex != InFmtCtx.AudioIndex)
                return;

            ret = InFmtCtx.Decode(packet, frame);
            if (ret < 0)
                return;
        }
        catch (Exception ex) { }

        frame.TimeBase = InFmtCtx.AudioStream.TimeBase;

        if (target_fmt == 0)
        {
            SampleRate = frame.SampleRate;
            ChannelNum = frame.Channels;
            (AVSampleFormat avFmt, BufferFormat alFmt) = SilkOpenALUtils.AVSampleFmtToALFmt(frame.AudioFormat, frame.Channels);
            target_fmt = alFmt;
            out_sample_fmt = avFmt;
            PerSampleSize = ffmpeg.av_get_bytes_per_sample(out_sample_fmt);
            Fifo ??= new FAudioFifo(out_sample_fmt, ChannelNum);

            SwrCtx ??= new FSwrContext(frame, out_sample_fmt);
            if (SwrCtx.Inited == false)
                return;

        }

        if (Fifo == null)
            throw new ArgumentException("AudioDecodeUtil->_fifo初始化异常");

        PcmFrame pcm = null;
        try
        {
            pcm = SwrCtx.ConvertAudioPcmData(frame, out_sample_fmt);
            if (pcm.NbSamples > 0)
                Fifo.Enqueue(pcm.Pcm, pcm.NbSamples);
        }
        finally
        {
            pcm?.Dispose();
        }
        return;
    }

    #endregion

}